home *** CD-ROM | disk | FTP | other *** search
/ Celestin Apprentice 5 / Apprentice-Release5.iso / Source Code / C / Applications / Python 1.3.3 / Python 133 68K / Demo / www / waisqp.py < prev    next >
Text File  |  1996-05-20  |  9KB  |  438 lines

  1. # Parser for WAIS question files.
  2. # The syntax is (I am really making this up, there is no decent grammar):
  3. # file: node
  4. # node: record | list
  5. # record: '(' keyword (keyword value)* ')'
  6. # list: '(' record* ')'
  7. # value: string | keyword | othertoken | node | '#' bytelist
  8. # bytelist: '(' number* ')'
  9. # Tokens are really almost anything, only strings are treated special;
  10. # keywords are tokens starting with ':'.
  11.  
  12. import regex
  13. import string
  14.  
  15.  
  16. # Class representing a record.
  17. # This is accessed as if it is a dictionary.
  18. # Limited sequential access is also supported: "for k, v in r: ..."
  19. #
  20. class Record:
  21.     #
  22.     def __init__(self, type):
  23.         self.type = type
  24.         self.itemlist = []
  25.     #
  26.     def __repr__(self):
  27.         s = '(:' + self.type + '\n'
  28.         for k, v in self.itemlist:
  29.             v = str(v)
  30.             if '\n' in v:
  31.                 lines = string.splitfields(v, '\n')
  32.                 v = string.joinfields(lines, '\n  ')
  33.             s = s + '  :' + k + ' ' + v + '\n'
  34.         s = s + ')'
  35.         return s
  36.     #
  37.     def __setitem__(self, keyword, value):
  38.         for i in range(len(self.itemlist)):
  39.             if keyword == self.itemlist[i][0]:
  40.                 self.itemlist[i] = (keyword, value)
  41.                 return
  42.         self.itemlist.append((keyword, value))
  43.     #
  44.     def __delitem__(self, keyword):
  45.         for i in range(len(self.itemlist)):
  46.             if keyword == self.itemlist[i][0]:
  47.                 del self.itemlist[i]
  48.                 return
  49.         raise KeyError, 'keyword not in Record: ' + repr(keyword)
  50.     #
  51.     def __getitem__(self, keyword):
  52.         if type(keyword) == type(0):
  53.             # Sequence type access
  54.             return self.itemlist[keyword]
  55.         # Mapping type access
  56.         for k, v in self.itemlist:
  57.             if k == keyword: return v
  58.         raise KeyError, 'keyword not in Record: ' + repr(keyword)
  59.     #
  60.     def __len__(self):
  61.         return len(self.itemlist)
  62.     #
  63.     def keys(self):
  64.         keys = []
  65.         for k, v in self.itemlist:
  66.             keys.append(k)
  67.         return keys
  68.     #
  69.     def has_key(self, keyword):
  70.         for k, v in self.itemlist:
  71.             if k == keyword: return 1
  72.         return 0
  73.     #
  74.     def gettype(self):
  75.         return self.type
  76.  
  77.  
  78. # Class representing a list of values.
  79. #
  80. class List:
  81.     #
  82.     def __init__(self, *args):
  83.         self.list = []
  84.         for item in args:
  85.             self.list.append(item)
  86.     #
  87.     def __repr__(self):
  88.         s = '(\n'
  89.         for item in self.list:
  90.             item = str(item)
  91.             if '\n' in item:
  92.                 lines = string.splitfields(item, '\n')
  93.                 item = string.joinfields(lines, '\n  ')
  94.             s = s + '  ' + item + '\n'
  95.         s = s + ')'
  96.         return s
  97.     #
  98.     def append(self, item):
  99.         self.list.append(item)
  100.     #
  101.     def insert(self, i, item):
  102.         self.list.insert(i, item)
  103.     #
  104.     def remove(self, item):
  105.         self.list.remove(item)
  106.     #
  107.     def __len__(self):
  108.         return len(self.list)
  109.     #
  110.     def __getitem__(self, i):
  111.         return self.list[i]
  112.     #
  113.     def __setitem__(self, i, value):
  114.         self.list[i] = value
  115.     #
  116.     def __delitem__(self, i):
  117.         del self.list[i]
  118.     #
  119.     def __getslice__(self, i, j):
  120.         new = List()
  121.         for item in self.list[i:j]:
  122.             new.append(item)
  123.         return new
  124.  
  125.  
  126. # Class representing a list of bytes.
  127. #
  128. class BytesList:
  129.     #
  130.     def __init__(self):
  131.         self.bytes = ''
  132.     #
  133.     def __repr__(self):
  134.         s = '#('
  135.         for byte in self.bytes:
  136.             s = s + ' ' + str(ord(byte))
  137.         s = s + ' )'
  138.         return s
  139.     #
  140.     def append(self, value):
  141.         try:
  142.             i = string.atoi(value)
  143.         except string.atoi_error:
  144.             raise SyntaxError, (value, 'byte')
  145.         try:
  146.             c = chr(i)
  147.         except ValueError:
  148.             raise SyntaxError, (value, 'byte in 0..255')
  149.         self.bytes = self.bytes + c
  150.  
  151.  
  152. # Regular expressions used by the tokenizer, and "compiled" versions
  153. #
  154. wspat = '\([ \t\n\r\f]+\|;.*\n\)*'
  155. tokenpat = '[()#"]\|[^()#"; \t\n\r\f]+'
  156. stringpat = '"\(\\\\.\|[^\\"]\)*"'    # "\(\\.\|[^\"]\)*"
  157. wsprog = regex.compile(wspat)
  158. tokenprog = regex.compile(tokenpat)
  159. stringprog = regex.compile(stringpat)
  160.  
  161.  
  162. # Parser base class without look-ahead.
  163. # Instantiate each time you want to parse a file.
  164. #
  165. class RealBaseParser:
  166.     #
  167.     def __init__(self, input):
  168.         #
  169.         # 'input' should have a parameterless method readline()
  170.         # which returns the next line, including trailing '\n',
  171.         # or the empty string if there is no more data.
  172.         # An open file will do nicely, as does an instance
  173.         # of StringInput below.
  174.         #
  175.         self.input = input
  176.         self.lineno = 0
  177.         #
  178.         # Reset the scanner interface.
  179.         #
  180.         self.reset()
  181.     #
  182.     def reset(self):
  183.         self.nextline = ''
  184.         self.pos = 0
  185.         self.tokstart = 0
  186.         self.eofseen = 0
  187.     #
  188.     # The real work of getting a token is done here.
  189.     # This is the first place place to look if you think
  190.     # the parser is too slow.
  191.     #
  192.     def getnexttoken(self):
  193.         while 1:
  194.             k = wsprog.match(self.nextline, self.pos)
  195.             if k < 0:
  196.                 raise SyntaxError, ('', 'whitespace')
  197.             self.pos = self.pos + k
  198.             k = tokenprog.match(self.nextline, self.pos)
  199.             if k >= 0:
  200.                 break
  201.             #
  202.             # End of line hit
  203.             #
  204.             if self.eofseen:
  205.                 self.nextline = ''
  206.             else:
  207.                 self.nextline = self.input.readline()
  208.             self.pos = self.tokstart = 0
  209.             if not self.nextline:
  210.                 if self.eofseen:
  211.                     raise EOFError
  212.                 self.eofseen = 1
  213.                 return ''
  214.             self.lineno = self.lineno + 1
  215.         #
  216.         # Found a token
  217.         #
  218.         self.tokstart, self.pos = self.pos, self.pos + k
  219.         token = self.nextline[self.tokstart:self.pos]
  220.         if token == '"':
  221.             #
  222.             # Get the whole string -- may read more lines
  223.             #
  224.             k = stringprog.match(self.nextline, self.tokstart)
  225.             while k < 0:
  226.                 cont = self.input.readline()
  227.                 if not cont:
  228.                     k = len(self.nextline) - self.tokstart
  229.                     break
  230.                 self.nextline = self.nextline + cont
  231.                 self.lineno = self.lineno + 1
  232.                 k = stringprog.match(self.nextline, \
  233.                     self.tokstart)
  234.             self.pos = self.tokstart + k
  235.             token = self.nextline[self.tokstart:self.pos]
  236.         return token
  237.     #
  238.     # Default error handlers.
  239.     #
  240.     def reporterror(self, filename, message, fp):
  241.         fp.write(filename)
  242.         fp.write(':' + `self.lineno` + ': ')
  243.         fp.write(message)
  244.         fp.write('\n')
  245.         self.printerrorline(fp)
  246.     #
  247.     def printerrorline(self, fp):
  248.         line = self.nextline
  249.         fp.write(line)
  250.         if line[-1:] <> '\n':
  251.             fp.write('\n')
  252.         for i in range(len(line)):
  253.             if i >= self.tokstart:
  254.                 n = max(1, self.pos - i)
  255.                 fp.write('^'*n)
  256.                 break
  257.             elif line[i] == '\t':
  258.                 fp.write('\t')
  259.             elif ' ' <= line[i] < '\177':
  260.                 fp.write(' ')
  261.         fp.write('\n')
  262.  
  263.  
  264. # Parser base class.  Instantiate each time you want to parse a file.
  265. # This supports a single token look-ahead.
  266. #
  267. class BaseParser(RealBaseParser):
  268.     #
  269.     def reset(self):
  270.         RealBaseParser.reset(self)
  271.         self.pushback = ''
  272.     #
  273.     def peektoken(self):
  274.         if not self.pushback:
  275.             self.pushback = self.getnexttoken()
  276.         return self.pushback
  277.     #
  278.     def gettoken(self):
  279.         if self.pushback:
  280.             token = self.pushback
  281.             self.pushback = ''
  282.         else:
  283.             token = self.getnexttoken()
  284.         if token == '':
  285.             raise EOFError
  286.         return token
  287.     #
  288.     def ungettoken(self, token):
  289.         if self.pushback:
  290.             raise AssertError, 'more than one ungettoken'
  291.         # print 'pushback:', token
  292.         self.pushback = token
  293.  
  294.  
  295. # Parser for a node.  Instantiate, and gell getnode() to parse a node.
  296. #
  297. class Parser(BaseParser):
  298.     #
  299.     # Parse a node.  This is highly recursive.
  300.     #
  301.     def getnode(self):
  302.         self.open()
  303.         # This can be either a list or a record
  304.         if self.peektoken() in ('(', ')'): # It's a list
  305.             list = List()
  306.             while self.more():
  307.                 list.append(self.getnode())
  308.             self.close()
  309.             return list
  310.         # Not a list, must be a record
  311.         type = self.getkeyword()
  312.         rec = Record(type)
  313.         while self.more():
  314.             keyword = self.getkeyword()
  315.             value = self.getvalue()
  316.             rec[keyword] = value
  317.         self.close()
  318.         return rec
  319.     #
  320.     def getkeyword(self):
  321.         t = self.gettoken()
  322.         if t[0] <> ':' or t == ':':
  323.             raise SyntaxError, (t, ':<keyword>')
  324.         return t[1:]
  325.     #
  326.     def getvalue(self):
  327.         t = self.peektoken()
  328.         if t == '(':
  329.             return self.getnode()
  330.         if t == '#':
  331.             self.expect('#')
  332.             return self.getbyteslist()
  333.         if t == ')':
  334.             raise SyntaxError, (t, '<value>')
  335.         return self.gettoken()
  336.     #
  337.     def getbyteslist(self):
  338.         bytes = BytesList()
  339.         self.open()
  340.         while self.more():
  341.             bytes.append(self.getbyte())
  342.         self.close()
  343.         return bytes
  344.     #
  345.     def getbyte(self):
  346.         return self.gettoken()
  347.     #
  348.     # Shorthands for frequently occurring parsing operations
  349.     #
  350.     def open(self):
  351.         self.expect('(')
  352.     #
  353.     def close(self):
  354.         self.expect(')')
  355.     #
  356.     def expect(self, exp):
  357.         t = self.gettoken()
  358.         if t <> exp:
  359.             raise SyntaxError, (t, exp)
  360.     #
  361.     def more(self):
  362.         if self.peektoken() == ')':
  363.             return 0
  364.         else:
  365.             return 1
  366.  
  367.  
  368. # A class to parse from a string
  369. #
  370. class StringInput:
  371.     #
  372.     def __init__(self, string):
  373.         self.string = string
  374.         self.pos = 0
  375.     #
  376.     def __repr__(self):
  377.         return '<StringInput instance, string=' + `self.string` \
  378.             + ', pos=' + `self.pos` + '>'
  379.     #
  380.     def readline(self):
  381.         string = self.string
  382.         i = self.pos
  383.         n = len(string)
  384.         while i < n:
  385.             if string[i] == '\n':
  386.                 i = i+1
  387.                 break
  388.             i = i+1
  389.         string = string[self.pos : i]
  390.         self.pos = i
  391.         return string
  392.  
  393.  
  394. # Convenience routines to parse a file
  395. #
  396. def parsefile(filename):
  397.     f = open(filename, 'r')
  398.     p = Parser(f)
  399.     result = p.getnode()
  400.     f.close()
  401.     return result
  402. #
  403. def parse(f):
  404.     p = Parser(f)
  405.     return p.getnode()
  406.  
  407.  
  408. # Test driver for tokenizer -- reads from stdin
  409. #
  410. def testtokenizer():
  411.     import sys
  412.     p = Parser(sys.stdin)
  413.     try:
  414.         while 1: p.gettoken()
  415.     except EOFError:
  416.         print 'EOF'
  417.     except SyntaxError, msg:
  418.         p.reporterror('<stdin>', 'Syntax error: ' + msg, sys.stderr)
  419.  
  420.  
  421. # Test driver for parser -- reads from stdin
  422. #
  423. def testparser():
  424.     import sys
  425.     p = Parser(sys.stdin)
  426.     try:
  427.         x = p.getnode()
  428.     except EOFError:
  429.         print 'unexpected EOF at line', p.lineno
  430.         return
  431.     except SyntaxError, msg:
  432.         if type(msg) == type(()):
  433.             gotten, expected = msg
  434.             msg = 'got ' + `gotten` + ', expected ' + `expected`
  435.         p.reporterror('<stdin>', 'Syntax error: ' + msg, sys.stderr)
  436.         return
  437.     print x
  438.